Phase 1.A–1.D depth, Track 2 audit, decision-gate v3 + Live 12 catalog completion - #18
Conversation
…+ catalog completion
Substantial measurement-depth pass on the ASA Phase 1 pipeline plus a
trustworthy multi-model decision-gate harness. Default Gemini model
(gemini-2.5-flash) now PASSes the stem-aware gate on the Vtss bench
track; Live 12 catalog completion drops UNKNOWN_PARAMETER 16 → 1
across all v3.1 snapshots.
## Depth additions (Phase 1.A – 1.D)
- Phase 1.A cheap wins (5): per-frame short-term LUFS curve, per-frame
7-band spectral balance time series, instantaneous tempoCurve,
per-second stereo correlationCurve, 256-point arrangement
noveltyCurve (up from 64).
- Phase 1.B stem-first refactor: per-stem subtree under
``stemAnalysis.{drums,bass,other,vocals}`` with the same shape as
the full-mix analyzers. Phase 2 can now cite element-specific
measurements.
- Phase 1.C #0 real time-signature detection (onset-accent
autocorrelation, replaces the always-4/4 fallback).
- Phase 1.C #1 per-band transient density across the 7
spectralBalance bands (kick / hi-hat density anchors).
- Phase 1.C #2 per-band stereo correlations (Utility-width-per-band
recommendations).
- Phase 1.C #3 ``grooveDetail.perDrumSwing.{kick,snare,hihat}`` from
the mid-band beat-loudness signal.
- Phase 1.C #4 snareDetail / hihatDetail band-limited drum character
(hits, attack sharpness, body/snap energy ratio, decay).
- Phase 1.C #5 saturationDetail (clipped sample count, peakRatio95to50,
rmsToPeakRatioDb, saturationLikely).
- Phase 1.C #6 32nd-note sidechain resolution + envelopeShape32
(legacy envelopeShape preserved via max-pairing downsample);
pumpingRate now four-valued including thirty_second.
- Phase 1.D #5 RT60 reverb per stem with perBandRt60 (low / lowMids /
highMids / highs) + preDelayMs via Schroeder-equivalent slope fit.
Wired through stemAnalysis.{stem}.reverbDetail.
## Track 2 audit pass 1 + measurement-quality fixes
- New ``scripts/audit_pass1.py`` harness — structured 12-section
markdown audit comparing real-track Phase 1 measurements against
declared ground truth and sanity rules.
- Bug fix: ``bassDetail.averageDecayMs`` was 0 ms on real bass
material (Codex PDF finding reproduced). Root cause: decay loop
searched from onset rather than peak, and checked the raw
oscillating waveform rather than an envelope. Fixed with a 20 ms
RMS envelope + peak-anchored search. Synthetic validation: 350 ms
theoretical → 346 ms measured; 35 ms → 37 ms. Real-track Vtss:
0 ms (FAIL) → 66–125 ms (PASS).
- Bug fix: ``vocalDetail.hasVocals = true`` false positive on
instrumental synth content. Added temporal-formant-stability check
(sustained synth leads have near-static "formants"; real vocals
shift formants 100+ Hz with syllables); tightened formant tolerance
200 → 100 Hz; raised threshold 0.45 → 0.55. No-stem run on Vtss now
correctly reports hasVocals=false at conf 0.48.
- Pre-existing import bugs in ``analyze_segments.py``:
``_compute_stereo_metrics`` (from dsp_utils) and ``Counter`` (from
collections) were undefined. ``segmentStereo`` + ``chordDetail``
now populate correctly.
## Phase 2 contract + validator + decision gate
- Phase 2 prompt v3: per-stem path enumeration, citation contract
(``phase1Fields`` required on every recommendation), "Phase-2
self-reference is not a citation" rule, dedicated sections for
snare/hi-hat character, saturation, sidechain envelope, reverb
detail.
- Phase 2 schema (``server_phase2.py``): ``phase1Fields`` array
required on ``mixAndMasterChain``, ``abletonRecommendations``, and
``secretSauce.workflowSteps`` items.
- Frontend validator (``phase2Validator.ts``) overhaul:
* 8 violation types (NUMERIC_OVERRIDE, GENRE_IGNORES_DSP,
BOUNDS_VIOLATION, MISSING_CITATION, TRIVIAL_CITATIONS,
NEW_FIELD_UNCITED, LOW_CONFIDENCE_NOT_HEDGED, RECOMMENDATION_SALVAGED).
* Bidirectional + wildcard ``pathCoversTracked`` matcher so leaf
citations satisfy parent tracked paths and ``stemAnalysis.*``
wildcards work.
* 19 tracked Phase 1.A + Phase 1.C/D field paths (was 7).
- Decision-gate vitests (4): single-, multi-, real-, and stem-aware
multi-model comparators that read ``/tmp/decision_gate_*.json``
snapshots and emit per-model markdown reports.
## Live 12 catalog completion (UNKNOWN_PARAMETER closure)
- Auto Filter: ``parameterAliases`` flat-map
``{"Filter Resonance": "Resonance", "Filter Frequency": "Frequency"}``.
Closes the instrument-side long-form naming bleed.
- Glue Compressor: ``allowedParameters`` expanded by 4
(Range, Sidechain, Sidechain Gain, Sidechain Dry/Wet).
- ``_validate_phase2_catalog_entry`` resolves aliases before the
membership check; startup-time loader validates the new
``parameterAliases`` shape.
- New ``Phase2CatalogValidationTests`` (11 cases) covers alias
resolution, expansion, scoping per-device, and negative cases.
- New ``scripts/replay_catalog_validation.py`` — no-Gemini-spend
harness that re-validates v3.1 snapshots against the live catalog.
Verdict: UNKNOWN_PARAMETER drops 16 → 1 across all 8 snapshots;
the remaining hit is ``EQ Eight / "Band 8 Filter Type"`` (separate
device, v3.2-target).
## Test state
- Backend: 374 tests pass (was 363; +11 ``Phase2CatalogValidationTests``).
- Frontend: 296 tests pass (was 286; +10 validator bidirectional /
wildcard tests).
- UI lint clean (``tsc --noEmit``).
## Out of scope / follow-ups (documented in SESSION_BLITZ + plan)
- Pro-model citation-depth iteration (3-pro and 3.1-pro still cite
far less Phase 1.C/D than 2.5-flash).
- gemini-3.1-pro-preview ``DROPPED_INVALID_ARRAY_ITEM`` salvage
regression seen in earlier v3 runs.
- Compressor "Sustain", "Ableton Project Settings", "Mixer" device
hits — v3.2 prompt-fix targets.
- Phase 1.D #1 madmom integration (prototype proven, install path
documented at ``.runtime/reports/madmom_install_prototype_2026-05-12.md``).
- Phase 1.D #2 chord progression deeper labelling.
- ``JSON_SCHEMA.md`` documentation of the catalog schema
(``parameterAliases`` field).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
slittycode
left a comment
There was a problem hiding this comment.
Verdict: REQUEST CHANGES
Summary
Large but coherent Phase 1 depth + validation gate pass. The type split (types.ts → types/{measurement,interpretation,backend}.ts) is clean, the Phase 1 contract is purely additive, and the phase boundary rules are respected throughout. The decision is forced by missing unit tests on the new Phase 1.C analyzers and dsp_utils.py — the CLAUDE.md bar is explicit here and several non-trivial functions shipped with zero test coverage.
Findings
Blocking
1. Phase 1.C analyzers have no unit test class (analyze_detection.py, analyze.py)
analyze_snare_detail, analyze_hihat_detail, _analyze_band_drum_detail, analyze_per_band_transient_density, and analyze_saturation_detail are new non-trivial functions. Their only coverage is that the field name appears in EXPECTED_TOP_LEVEL_KEYS. That's a schema-presence check, not a behavioral test.
_analyze_band_drum_detail alone has ~60 lines of band-pass isolation, energy-envelope peak detection, decay measurement, and a body/snap ratio computation. analyze_saturation_detail counts clipped samples and computes a 95th/50th percentile ratio. analyze_per_band_transient_density runs a seven-band onset detector. None of these have assertions against synthetic inputs.
Minimum needed: null-input guard, schema fields present, and at least one case asserting non-degenerate output for a signal that should trigger the detector. Same pattern as the existing ReverbDetailTests/BassDetailTests classes.
2. _run_per_stem_analyses has no tests (analyze.py)
This is the Phase 1.B stem overlay orchestrator — the function the PR headline rests on. It's ~85 lines of exception-wrapped dispatch over drums/bass/other/vocals stems. If an analyzer throws on a specific stem, that stem's entry is silently dropped. No test exercises the "one stem fails, others succeed" path, nor the "stems=None → returns None" guard, nor that the returned dict shape matches StemAnalysis.
A test using four synthetic per-stem WAVs (or mocked _load_stem_mono) would catch regressions here.
3. dsp_utils.py has no unit tests (new file, 372 lines)
_pearson_corr, _downsample_lufs_array, _compute_tempo_curve_from_ticks, _compute_stereo_correlation_curve, _downsample_band_energies_curve are shared math utilities. A numerical bug in any of them propagates to every analyzer that imports them. _pearson_corr in particular is called inside the sidechain dip-correlation loop and the stereo band-correlation computation — both Phase 1 outputs used by the decision gate.
4. No regression test for averageDecayMs > 0 (analyze.py)
This is the PR's stated headline bug fix: the old decay loop ran on the raw oscillating bass signal and hit threshold in <1 ms on every note. The envelope-based fix is correct. But there is no test asserting averageDecayMs > 50 (or any positive bound) for a synthetic bass pulse. Without it, the bug can regress silently. BassDetailTests has a stem-preference test added in this PR but doesn't assert the decay value.
Should fix
5. pathCoversTracked inline comment is wrong (apps/ui/src/services/phase2Validator.ts:552-553)
"Wildcards only match a single segment —
stemAnalysis.*won't matchstemAnalysis.drums.spectralBalance"
The implementation contradicts this. The ts.length <= cs.length branch runs for ts = ["stemAnalysis","*"] (len 2) against cs = ["stemAnalysis","drums","spectralBalance"] (len 3), checks both tracked segments, * matches drums, loop ends — returns true. So stemAnalysis.* does match stemAnalysis.drums.spectralBalance under the current code.
No functional breakage today because no tracked path is actually stemAnalysis.*. But the comment will mislead the next person who adds a short wildcard tracked path and expects single-segment matching.
Worth considering
6. fundamentalHz in analyze_bass_detail uses ZCR estimation on a lowpass-filtered signal
ZCR/2 equals f₀ only for pure sinusoids. A real bass line with a strong second harmonic at 2×f₀ will drive the ZCR upward, biasing the estimate. The clamp max(30.0, min(120.0, zcr/2.0)) limits damage but a 35 Hz sub with strong 70 Hz harmonic energy can read as ~55 Hz. JSON_SCHEMA.md doesn't flag this as approximate. If Phase 2 cites bassDetail.fundamentalHz for a sub-bass filter-frequency recommendation, the error could put an 18 dB/oct cut 20 Hz above the actual fundamental. Worth a one-line schema note ("approximation; harmonic-rich signals bias upward; prefer pitchDetail when stems are available").
Test results
PR description claims 374 backend / 296 frontend pass. Could not execute the suite locally (Essentia/torch not in this environment). Test count and class names in the diff are consistent with the description. Failures: none observed in code review.
Phase boundary check
Clean. _finalize_style_profile_authoritative_measurements in server_phase2.py explicitly overwrites Gemini's styleProfile.authoritativeMeasurements.{bpm,key,timeSignature} with Phase 1 measured values after every response, and emits AUTHORITATIVE_MEASUREMENT_OVERRIDDEN warnings when it had to do so. Phase 2/3 code reads Phase 1 output but never re-derives or mutates it. The new validator checks (citation diversity, low-confidence hedging, salvage warnings) surface Phase 2 misbehavior as violations without touching Phase 1 data.
Generated by Claude Code
…rator, and dsp_utils (#19) Closes the test-coverage gaps flagged in the PR review. Backend: - test_analyze.py: new BandDrumDetailTests, SnareDetailTests, HihatDetailTests, TransientDensityDetailTests, SaturationDetailTests, RunPerStemAnalysesTests. Each covers null/short/silent input guards, schema-field presence, and at least one behavioral assertion against a synthetic signal that should trip the detector. RunPerStemAnalysesTests covers stems=None, empty dict, all loads failing, mono+stereo success, partial-analyzer failure, and the stereo-load-skipped path. - BassDetailTests: regression test for averageDecayMs > 30 ms on a synthetic decaying-pulse signal — guards the envelope-based fix from sliding back to the pre-fix sub-millisecond range. - test_dsp_utils.py (new): direct tests for _pearson_corr, _downsample_lufs_array, _downsample_band_energies_curve, _compute_tempo_curve_from_ticks, and _compute_stereo_correlation_curve, with closed-form expected values for perfect/anti/orthogonal correlation, constant-input NaN handling, tempo-change detection, and the silent-sub None policy. Docs / comments: - JSON_SCHEMA.md: flag bassDetail.fundamentalHz as a ZCR approximation that biases upward on harmonic-rich basses; steer Phase 2 to prefer pitchDetail when stems are available and avoid narrow filter-Q moves within ±15 Hz of the value. - phase2Validator.ts: rewrite the pathCoversTracked docstring so it matches what the code actually does (a wildcard token covers all longer citation paths under the same prefix; "stemAnalysis.*" does match "stemAnalysis.drums.spectralBalance"). Co-authored-by: Claude <noreply@anthropic.com>
…brosa+Viterbi Replaces the 5-frame median-filter smoothing of Essentia's per-frame ChordsDetection labels (PR #18) with a 25-state (12 major + 12 minor + 'N' no-chord) Viterbi decoder over librosa.feature.chroma_cqt. The HMM self-loop prior produces fewer, more stable segments on hard material (electronic, modal harmony) and exposes a per-segment confidence we can hedge against. Public schema preserved — chordTimeline[].label still matches dominantChords convention; chordChangeCount recomputed from the new timeline. Backend (apps/backend): - analyze_segments.py: new helpers _chord_templates_25, _viterbi_chord_timeline, _state_label_short/long, _normalize_chord_label_for_compare. Viterbi decoder uses L1 mass-overlap for emission (N-safe on dense chroma) and L2 cosine similarity for the per-segment confidence metric (intuitive [0, 1] range, 0.5 = noise floor, 0.7+ = clean match). analyze_chords now also returns chordTimelineSource = 'librosa_viterbi' and chordTimelineAgreement (bool | None) computed against Essentia's dominantChords[0] after enharmonic normalization (Eb <-> D#, Bb <-> A#, etc.). - JSON_SCHEMA.md: update chordTimeline row for the new engine and labelLong field; add rows for labelLong, chordTimelineSource, chordTimelineAgreement. - prompts/phase2_system.txt CHORD TIMELINE section: note the engine change, document labelLong, add chordTimelineAgreement hedging rule. - tests/test_analyze.py: new ChordTimelineViterbiTests class with 6 tests — synthetic C-major triad fixture, white-noise no-spurious- triads invariant, source/agreement field shape, chordChangeCount recompute, enharmonic normalization, and template-matrix structure. Frontend (apps/ui): - types/measurement.ts: ChordTimelineEntry gains optional labelLong; ChordDetail gains chordTimelineSource and chordTimelineAgreement. - services/backendPhase1Client.ts: replace the as-cast passthrough on chordDetail with a tolerant parseOptionalChordDetail helper modeled on parseOptionalRhythmTimeline. Malformed nested timeline entries are dropped (missing required keys, NaN confidence, endSec < startSec, non- records); confidence clamped to [0, 1]; segments sorted by startSec. Back-compat with payloads lacking labelLong. - services/phase2Validator.ts: add chordDetail.chordTimeline and chordDetail.chordChangeCount to PHASE1_NEW_FIELD_PATHS; map chordDetail.chordTimeline to chordDetail.chordStrength in CONFIDENCE_PAIRS so the existing LOW_CONFIDENCE_NOT_HEDGED rule covers timeline citations. No new validators or shape changes. - services/fieldAnalytics.ts: fix the existing chordDetail.chordProgression typo (real field is chordDetail.progression); add chordDetail.chordTimeline and chordDetail.chordChangeCount to the analytics tracking list. - tests/services/backendPhase1Client.test.ts: extend chord fixture with the new Viterbi fields; add 4 new tests covering the tolerant parser, back-compat with missing labelLong, malformed-segment filtering, and chordTimelineAgreement coercion. - tests/services/phase2Validator.test.ts: extend the PHASE1_NEW_FIELD_PATHS guard with chord-timeline entries; add new-field-coverage tests for chordDetail.chordTimeline (cited vs uncited); add a low-confidence hedging test for a chord-citing recommendation with imperative text. Verification: - Backend: 439 tests pass (439 before; 6 new chord tests added, 0 regressions). On the Vtss-CantCatchMe.mp3 bench fixture: 15 segments, labels Eb/C/Cm/Db matching dominantChords ['Cm','Eb','C','F'], chordChangeCount 14, confidence range [0.54, 0.74], chordTimelineAgreement False (honest Eb-vs-Cm disagreement worth hedging on a track that bounces between relative major/minor). - Frontend: 303 tests pass, lint clean. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…brosa+Viterbi (#20) Replaces the 5-frame median-filter smoothing of Essentia's per-frame ChordsDetection labels (PR #18) with a 25-state (12 major + 12 minor + 'N' no-chord) Viterbi decoder over librosa.feature.chroma_cqt. The HMM self-loop prior produces fewer, more stable segments on hard material (electronic, modal harmony) and exposes a per-segment confidence we can hedge against. Public schema preserved — chordTimeline[].label still matches dominantChords convention; chordChangeCount recomputed from the new timeline. Backend (apps/backend): - analyze_segments.py: new helpers _chord_templates_25, _viterbi_chord_timeline, _state_label_short/long, _normalize_chord_label_for_compare. Viterbi decoder uses L1 mass-overlap for emission (N-safe on dense chroma) and L2 cosine similarity for the per-segment confidence metric (intuitive [0, 1] range, 0.5 = noise floor, 0.7+ = clean match). analyze_chords now also returns chordTimelineSource = 'librosa_viterbi' and chordTimelineAgreement (bool | None) computed against Essentia's dominantChords[0] after enharmonic normalization (Eb <-> D#, Bb <-> A#, etc.). - JSON_SCHEMA.md: update chordTimeline row for the new engine and labelLong field; add rows for labelLong, chordTimelineSource, chordTimelineAgreement. - prompts/phase2_system.txt CHORD TIMELINE section: note the engine change, document labelLong, add chordTimelineAgreement hedging rule. - tests/test_analyze.py: new ChordTimelineViterbiTests class with 6 tests — synthetic C-major triad fixture, white-noise no-spurious- triads invariant, source/agreement field shape, chordChangeCount recompute, enharmonic normalization, and template-matrix structure. Frontend (apps/ui): - types/measurement.ts: ChordTimelineEntry gains optional labelLong; ChordDetail gains chordTimelineSource and chordTimelineAgreement. - services/backendPhase1Client.ts: replace the as-cast passthrough on chordDetail with a tolerant parseOptionalChordDetail helper modeled on parseOptionalRhythmTimeline. Malformed nested timeline entries are dropped (missing required keys, NaN confidence, endSec < startSec, non- records); confidence clamped to [0, 1]; segments sorted by startSec. Back-compat with payloads lacking labelLong. - services/phase2Validator.ts: add chordDetail.chordTimeline and chordDetail.chordChangeCount to PHASE1_NEW_FIELD_PATHS; map chordDetail.chordTimeline to chordDetail.chordStrength in CONFIDENCE_PAIRS so the existing LOW_CONFIDENCE_NOT_HEDGED rule covers timeline citations. No new validators or shape changes. - services/fieldAnalytics.ts: fix the existing chordDetail.chordProgression typo (real field is chordDetail.progression); add chordDetail.chordTimeline and chordDetail.chordChangeCount to the analytics tracking list. - tests/services/backendPhase1Client.test.ts: extend chord fixture with the new Viterbi fields; add 4 new tests covering the tolerant parser, back-compat with missing labelLong, malformed-segment filtering, and chordTimelineAgreement coercion. - tests/services/phase2Validator.test.ts: extend the PHASE1_NEW_FIELD_PATHS guard with chord-timeline entries; add new-field-coverage tests for chordDetail.chordTimeline (cited vs uncited); add a low-confidence hedging test for a chord-citing recommendation with imperative text. Verification: - Backend: 439 tests pass (439 before; 6 new chord tests added, 0 regressions). On the Vtss-CantCatchMe.mp3 bench fixture: 15 segments, labels Eb/C/Cm/Db matching dominantChords ['Cm','Eb','C','F'], chordChangeCount 14, confidence range [0.54, 0.74], chordTimelineAgreement False (honest Eb-vs-Cm disagreement worth hedging on a track that bounces between relative major/minor). - Frontend: 303 tests pass, lint clean. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Substantial measurement-depth pass on the ASA Phase 1 pipeline plus a trustworthy multi-model decision-gate harness.
Headline results
UNKNOWN_PARAMETER16 → 1 across all v3.1 snapshots via the no-Gemini-spend replay harness.bassDetail.averageDecayMs = 0(Codex PDF finding) andvocalDetail.hasVocals = truefalse positive on instrumental synth content.Diff scope
phase1Fieldsrequired on every recommendation, catalogparameterAliasesmechanism.Full per-stage narrative in
apps/backend/.runtime/reports/SESSION_BLITZ_2026-05-12.md(750 lines, four continuation appendices) — that file is gitignored (lives under.runtime/), so attached separately to the discussion if reviewers want it. The commit message above also covers the major beats.Tests
Phase2CatalogValidationTestsfor alias resolution + Glue Compressor expansion + negative cases).pathCoversTrackedbidirectional + wildcard tests + tracked-paths inventory guard).tsc --noEmit).apps/backend/scripts/replay_catalog_validation.py):UNKNOWN_PARAMETER16 → 1 across 8 v3.1 snapshots, no Gemini spend.What's intentionally NOT in this PR
Documented as follow-ups in the SESSION_BLITZ + plan files:
DROPPED_INVALID_ARRAY_ITEMsalvage regression observed in earlier v3 runs..runtime/reports/madmom_install_prototype_2026-05-12.md); not yet wired as conditional analyzer.JSON_SCHEMA.mddocumentation of the catalogparameterAliasesfield (flagged as a doc pass).Reviewer notes
src/(pertsconfig.json); test files and vitest configs are excluded by design.phase2Validator.tsis the load-bearing change for the gate's depth metric — tests attests/services/phase2Validator.test.tsexercise both directions plus the stem-wildcard semantics. Worth a closer read.tests/test_server.Phase2CatalogValidationTestsexercise the alias mechanism directly without Gemini mocking.🤖 Generated with Claude Code